package com.mc.fastkit.view.sheet

import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.core.animation.doOnEnd
import androidx.core.view.NestedScrollingParent3
import androidx.core.view.ViewCompat
import androidx.core.view.children
import com.mc.fastkit.R
import com.mc.fastkit.ext.cast
import com.mc.fastkit.ext.dp2pxi
import kotlin.math.abs

/**
 * SheetLayout
 * @author: MasterChan
 * @date: 2024-03-04 09:55
 */
open class SheetLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), NestedScrollingParent3 {

    /**
     * 是否启用拖拽
     */
    open var enableDrag = true

    /**
     * 是否处理Touch拦截事件，默认要处理
     */
    open var handleInterceptTouchEvent = true

    /**
     * 是否可隐藏
     */
    open var hideAble = true

    /**
     * 是否开启快速滑动
     */
    open var enableFling = true

    /**
     * 是否启用半展开模式，如果不启用，没有[SheetState.HalfExpanded]状态
     */
    open var enableHalfMode = false

    /**
     * 当前的状态
     */
    open var currentState: SheetState = SheetState.Expanded

    /**
     * [scrollTo]动画持续的时间
     */
    open var duration = 250L

    /**
     * 计算后的折叠高度
     */
    var collapsedHeight = 0
        private set

    /**
     * 半展开的高度
     */
    var halfModeHeight = -1
        private set

    /**
     * 高度剩余模式，收缩状态时，收缩高度为[getHeight]-[collapsedHeight]
     */
    var collapsedRemainderMode = false
        private set

    /**
     * 滑动到下一个状态的阈值
     */
    var nextStateRatio = 0.25f
        private set

    /**
     * Y方向上的fling速度
     */
    var flingVelocityY = 300f
        private set

    /**
     * 标识[onLayout]是否已调用，用于获取初始化时的Y坐标
     */
    private var isLayout = false

    /**
     * [currentState]为[SheetState.Expanded]时的Y坐标
     */
    var expandedY = 0f
        private set

    /**
     * [currentState]为[SheetState.Collapsed]时的Y坐标
     */
    var collapsedY = 0f
        private set

    /**
     * [currentState]为[SheetState.Hidden]时的Y坐标
     */
    var hiddenY = 0f
        private set

    /**
     * [currentState]为[SheetState.HalfExpanded]时的Y坐标，默认在展开和收缩状态的中间位置
     */
    var halfExpandedY: Float = 0.0f
        private set

    /**
     * 设置的折叠高度
     */
    private var _collapsedHeight = 0

    /**
     * 滑动动画
     */
    private var scrollAnimator: ValueAnimator? = null

    /**
     * 是否向上滑动
     */
    private var isScrollUp = false

    private var tracker: VelocityTracker? = null
    private var lastX = 0f
    private var lastY = 0f
    private val scrollListeners = mutableListOf<OnScrollListener>()
    private val stateChangedListeners = mutableListOf<OnStateChangedListener>()
    private var isNestedScrolling = false
    private val viewConfig = ViewConfiguration.get(context)

    init {
        this.initAttrs(attrs, defStyleAttr, defStyleRes)
    }

    open fun initAttrs(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {
        val a = context.obtainStyledAttributes(
            attrs, R.styleable.SheetLayout, defStyleAttr, defStyleRes
        )
        enableDrag = a.getBoolean(R.styleable.SheetLayout_mc_enableDrag, true)
        hideAble = a.getBoolean(R.styleable.SheetLayout_mc_hideAble, true)
        enableFling = a.getBoolean(R.styleable.SheetLayout_mc_enableFling, true)
        enableHalfMode = a.getBoolean(R.styleable.SheetLayout_mc_enableHalfMode, true)
        halfModeHeight = a.getDimensionPixelOffset(R.styleable.SheetLayout_mc_halfModeHeight, -1)
        currentState = a.getInteger(R.styleable.SheetLayout_mc_currentState, 1).toSheetState()
        duration = a.getInteger(R.styleable.SheetLayout_mc_duration, duration.toInt()).toLong()
        nextStateRatio = a.getFloat(R.styleable.SheetLayout_mc_nextStateRatio, nextStateRatio)
        flingVelocityY = a.getFloat(R.styleable.SheetLayout_mc_flingVelocityY, flingVelocityY)
        _collapsedHeight = a.getLayoutDimension(
            R.styleable.SheetLayout_mc_collapsedHeight, dp2pxi(200)
        )
        collapsedRemainderMode = a.getBoolean(
            R.styleable.SheetLayout_mc_collapsedRemainderMode, false
        )
        handleInterceptTouchEvent = a.getBoolean(
            R.styleable.SheetLayout_mc_handleInterceptTouchEvent, true
        )
        a.recycle()
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (expandedY != top.toFloat()) {
            scrollAnimator?.cancel()
            expandedY = top.toFloat()
            halfExpandedY = 0f
            collapsedY = 0f
            hiddenY = 0f
            collapsedHeight = 0
            calculateOffset()
            setCurrentState(currentState, isLayout, false)
            isLayout = true
        }
    }

    open fun calculateOffset() {
        val tempHeight = when (_collapsedHeight) {
            LayoutParams.MATCH_PARENT -> {
                measuredHeight
            }

            LayoutParams.WRAP_CONTENT -> {
                children.maxOfOrNull { it.measuredHeight } ?: measuredHeight
            }

            else -> _collapsedHeight
        }
        collapsedHeight = if (collapsedRemainderMode) height - tempHeight else tempHeight
        collapsedY = expandedY + measuredHeight - collapsedHeight
        halfExpandedY = if (halfModeHeight == -1) {
            expandedY + (collapsedY - expandedY) / 2
        } else {
            expandedY + measuredHeight - halfModeHeight
        }
        hiddenY = expandedY + measuredHeight.toFloat()
    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        if (!handleInterceptTouchEvent || !enableDrag) {
            return super.onInterceptTouchEvent(event)
        }
        if (scrollAnimator?.isRunning == true) {
            scrollAnimator?.cancel()
            return true
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isNestedScrolling = false
                currentState = SheetState.Dragging
                lastX = event.rawX
                lastY = event.rawY
                tracker = VelocityTracker.obtain()
                scrollAnimator?.cancel()
            }

            MotionEvent.ACTION_MOVE -> {
                if (!isNestedScrolling) {
                    val dx = event.rawX - lastX
                    var dy = event.rawY - lastY
                    lastX = event.rawX
                    lastY = event.rawY
                    if (abs(dy) > abs(dx)) {
                        isScrollUp = dy <= 0
                        dy = if (y + dy <= expandedY) expandedY - y else dy
                        if (!hideAble) {
                            if (y + dy > collapsedY) {
                                dy = collapsedY - y
                            }
                        }
                        y += dy
                        scrollListeners.forEach { it.onScroll(dy) }
                        tracker?.addMovement(event)
                    }
                }
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (!isNestedScrolling) {
                    if (tracker != null) {
                        tracker!!.computeCurrentVelocity(
                            1000, viewConfig.scaledMaximumFlingVelocity.toFloat()
                        )
                        if (enableFling && abs(tracker!!.yVelocity) >= flingVelocityY) {
                            finishScrollFling()
                        } else {
                            finishScroll()
                        }
                        tracker!!.recycle()
                        tracker = null
                    } else {
                        finishScroll()
                    }
                }
                isNestedScrolling = false
            }
        }
        return super.onInterceptTouchEvent(event)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        super.onTouchEvent(event)
        if (!enableDrag) {
            return super.onTouchEvent(event)
        }
        if (scrollAnimator?.isRunning == true) {
            scrollAnimator?.cancel()
            return true
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isNestedScrolling = false
                currentState = SheetState.Dragging
                lastX = event.rawX
                lastY = event.rawY
                tracker = VelocityTracker.obtain()
                scrollAnimator?.cancel()
            }

            MotionEvent.ACTION_MOVE -> {
                if (!isNestedScrolling) {
                    val dx = event.rawX - lastX
                    var dy = event.rawY - lastY
                    lastX = event.rawX
                    lastY = event.rawY
                    if (abs(dy) > abs(dx)) {
                        isScrollUp = dy <= 0
                        dy = if (y + dy <= expandedY) expandedY - y else dy
                        if (!hideAble) {
                            if (y + dy > collapsedY) {
                                dy = collapsedY - y
                            }
                        }
                        y += dy
                        scrollListeners.forEach { it.onScroll(dy) }
                        tracker?.addMovement(event)
                    }
                }
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (!isNestedScrolling) {
                    if (tracker != null) {
                        tracker!!.computeCurrentVelocity(
                            1000, viewConfig.scaledMaximumFlingVelocity.toFloat()
                        )
                        if (enableFling && abs(tracker!!.yVelocity) >= flingVelocityY) {
                            finishScrollFling()
                        } else {
                            finishScroll()
                        }
                        tracker!!.recycle()
                        tracker = null
                    } else {
                        finishScroll()
                    }
                }
                isNestedScrolling = false
            }
        }
        return !isNestedScrolling
    }

    private fun Int.toSheetState(): SheetState {
        return when (this) {
            0 -> SheetState.Dragging
            1 -> SheetState.Expanded
            2 -> SheetState.HalfExpanded
            3 -> SheetState.Collapsed
            4 -> SheetState.Hidden
            5 -> SheetState.Settling
            else -> SheetState.Collapsed
        }
    }

    /**
     * 修改当前的状态
     * @param state 修改后的状态
     * @param smoothScroll 滚动动画
     */
    open fun setCurrentState(
        state: SheetState,
        smoothScroll: Boolean = true,
        dispatchListener: Boolean = true
    ) {
        val y = when (state) {
            SheetState.Expanded -> expandedY
            SheetState.HalfExpanded -> halfExpandedY
            SheetState.Collapsed -> collapsedY
            SheetState.Hidden -> hiddenY
            else -> null
        }
        if (y != null) {
            scrollTo(state, y, smoothScroll, dispatchListener)
        }
    }

    /**
     * 滑动[targetY]的距离后达到[state]状态
     * @param state 滚动后的状态
     * @param targetY 滚动的距离
     * @param smoothScroll 是否有滚动动画
     * @param dispatchListener 是否分发事件
     */
    open fun scrollTo(
        state: SheetState,
        targetY: Float,
        smoothScroll: Boolean = true,
        dispatchListener: Boolean = true
    ) {
        if (scrollAnimator?.isRunning == true) {
            scrollAnimator?.cancel()
        }
        if (smoothScroll) {
            currentState = SheetState.Settling
            var lastY = y
            scrollAnimator = ObjectAnimator.ofFloat(y, targetY)
            scrollAnimator!!.duration = duration
            scrollAnimator!!.addUpdateListener {
                val currentY = it.animatedValue.cast<Float>()
                y = currentY
                val offset = currentY - lastY
                lastY = currentY
                if (dispatchListener) {
                    scrollListeners.forEach { listener -> listener.onScroll(offset) }
                }
            }
            scrollAnimator!!.doOnEnd {
                if (dispatchListener) {
                    stateChangedListeners.forEach { it.onStateChanged(currentState, state) }
                }
                currentState = state
            }
            scrollAnimator!!.start()
        } else {
            y = targetY
            if (dispatchListener) {
                stateChangedListeners.forEach { it.onStateChanged(currentState, state) }
            }
            currentState = state
        }
    }

    /**
     * 结束滑动后，修复根据当前的Y坐标修正到正确的位置
     */
    protected open fun finishScroll() {
        if (enableHalfMode) {
            if (isScrollUp) {
                when {
                    //超过半展开状态1/4为展开状态
                    y < halfExpandedY - (halfExpandedY - expandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    //半展开状态的1/4到收缩状态的1/4之间为半展开状态
                    enableHalfMode && y < collapsedY - (collapsedY - halfExpandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.HalfExpanded)
                    }

                    else -> {
                        setCurrentState(SheetState.Collapsed)
                    }
                }
            } else {
                when {
                    y < expandedY + (halfExpandedY - expandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    y < halfExpandedY + (collapsedY - halfExpandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.HalfExpanded)
                    }

                    y < collapsedY + collapsedHeight * nextStateRatio -> {
                        setCurrentState(SheetState.Collapsed)
                    }

                    else -> {
                        setCurrentState(if (hideAble) SheetState.Hidden else SheetState.Collapsed)
                    }
                }
            }
        } else {
            if (isScrollUp) {
                when {
                    y < collapsedY - (collapsedY - expandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    else -> {
                        setCurrentState(SheetState.Collapsed)
                    }
                }
            } else {
                when {
                    y < expandedY + (collapsedY - expandedY) * nextStateRatio -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    y < collapsedY + collapsedHeight * nextStateRatio -> {
                        setCurrentState(SheetState.Collapsed)
                    }

                    else -> {
                        setCurrentState(if (hideAble) SheetState.Hidden else SheetState.Collapsed)
                    }
                }
            }
        }
    }

    protected open fun finishScrollFling() {
        if (enableHalfMode) {
            if (isScrollUp) {
                when {
                    y < collapsedY && y >= halfExpandedY -> {
                        setCurrentState(SheetState.HalfExpanded)
                    }

                    y < halfExpandedY && y >= expandedY -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    else -> {
                        setCurrentState(SheetState.Collapsed)
                    }
                }
            } else {
                when {
                    y > expandedY && y <= halfExpandedY -> {
                        setCurrentState(SheetState.HalfExpanded)
                    }

                    y > halfExpandedY && y <= collapsedY -> {
                        setCurrentState(SheetState.Collapsed)
                    }

                    else -> {
                        setCurrentState(if (hideAble) SheetState.Hidden else SheetState.Collapsed)
                    }
                }
            }
        } else {
            if (isScrollUp) {
                when {
                    y < collapsedY -> {
                        setCurrentState(SheetState.Expanded)
                    }

                    else -> {
                        setCurrentState(SheetState.Collapsed)
                    }
                }
            } else {
                when {
                    y <= collapsedY -> {
                        setCurrentState(SheetState.Collapsed)
                    }

                    else -> {
                        setCurrentState(if (hideAble) SheetState.Hidden else SheetState.Collapsed)
                    }
                }
            }
        }
    }

    override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
        isNestedScrolling = axes == ViewCompat.SCROLL_AXIS_VERTICAL
        return isNestedScrolling
    }

    override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
        if (scrollAnimator?.isRunning == true) {
            scrollAnimator?.cancel()
        }
    }

    override fun onNestedPreFling(p0: View, p1: Float, p2: Float): Boolean {
        //向上fling时滚动到下一个状态
        if (isScrollUp) {
            if (y < halfExpandedY) {
                setCurrentState(SheetState.Expanded)
                return true
            } else if (y < collapsedY && y > halfExpandedY && enableHalfMode) {
                setCurrentState(SheetState.HalfExpanded)
                return true
            } else if (!enableHalfMode && y < collapsedY) {
                setCurrentState(SheetState.Expanded)
                return true
            }
        }
        return super.onNestedPreFling(p0, p1, p2)
    }

    override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        if (enableDrag) {
            if (type == ViewCompat.TYPE_TOUCH) {
                isScrollUp = dy > 0
                if (isScrollUp) {
                    val offset = if (y - dy < expandedY) {
                        y - expandedY
                    } else {
                        dy.toFloat()
                    }
                    if (offset != 0f) {
                        currentState = SheetState.Dragging
                        y -= offset
                        consumed[1] = offset.toInt()
                        scrollListeners.forEach { it.onScroll(-offset) }
                    } else {
                        consumed[1] = 0
                    }
                }
            } else {
                isScrollUp = dy > 0
            }
        }
    }

    override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int,
        consumed: IntArray
    ) {
        if (enableDrag && type == ViewCompat.TYPE_TOUCH) {
            isScrollUp = dyUnconsumed > 0
            //上边界控制
            var dy = if (y - dyUnconsumed < expandedY) {
                y - expandedY
            } else {
                dyUnconsumed.toFloat()
            }
            //下边界控制
            dy = if (hideAble) {
                if (y - dy >= collapsedY + collapsedHeight) {
                    y - (collapsedY + collapsedHeight)
                } else {
                    dy
                }
            } else {
                if (y - dy >= collapsedY) {
                    y - collapsedY
                } else {
                    dy
                }
            }
            if (dy != 0f) {
                currentState = SheetState.Dragging
                y -= dy
                consumed[1] = dy.toInt()
                scrollListeners.forEach { it.onScroll(-dy) }
            } else {
                consumed[1] = 0
            }
        }
    }

    override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int
    ) {
    }

    override fun onStopNestedScroll(target: View, type: Int) {
        isNestedScrolling = false
        if (type == ViewCompat.TYPE_TOUCH) {
            if (currentState == SheetState.Dragging) {
                finishScroll()
            }
        }
    }

    open fun setEnableDrag(enableDrag: Boolean) = apply {
        this.enableDrag = enableDrag
    }

    open fun setHandleInterceptTouchEvent(isHandle: Boolean) = apply {
        this.handleInterceptTouchEvent = isHandle
    }

    open fun setHideAble(hideAble: Boolean) = apply {
        this.hideAble = hideAble
    }

    open fun setEnableHalfMode(enableHalfMode: Boolean) = apply {
        this.enableHalfMode = enableHalfMode
    }

    open fun setDuration(duration: Long) = apply {
        this.duration = duration
    }

    open fun setCollapsedHeight(collapsedHeight: Int) = apply {
        this._collapsedHeight = collapsedHeight
        calculateOffset()
        setCurrentState(currentState, dispatchListener = false)
    }

    open fun setCollapsedRemainderMode(remainderMode: Boolean) = apply {
        this.collapsedRemainderMode = remainderMode
        calculateOffset()
        setCurrentState(currentState, dispatchListener = false)
    }

    open fun setHalfModeHeight(height: Int) = apply {
        this.halfModeHeight = height
        calculateOffset()
        setCurrentState(currentState, dispatchListener = false)
    }

    open fun setNextStateRatio(nextStateRatio: Float) = apply {
        this.nextStateRatio = nextStateRatio
    }

    open fun setFlingVelocityY(flingVelocityY: Float) = apply {
        this.flingVelocityY = flingVelocityY
    }

    open fun addOnStateChangedListener(listener: OnStateChangedListener) = apply {
        stateChangedListeners.add(listener)
    }

    open fun removeOnStateChangedListener(listener: OnStateChangedListener) = apply {
        stateChangedListeners.remove(listener)
    }

    open fun addOnScrollListener(listener: OnScrollListener) = apply {
        scrollListeners.add(listener)
    }

    open fun removeOnScrollListener(listener: OnScrollListener) = apply {
        scrollListeners.remove(listener)
    }
}